/**
 * jQueryAnnex - Validation Plugin
 * Provides the means to validate (form-)values on the fly in the client.
 * This is especially useful if you develop JS-driven apps without classical forms and reloads.
 *
 * All methods in this plugin are derived from my PHP5-framework "HtmlForm", which also includes
 * JS-validation (coupled with server-side validation) for forms. The validation capabilities aim to
 * as convenient and complete as possible.
 *
 * There are generally two methods of defining validation: either use jQuery to select value-bearing tags and
 * objects and register a/several validator(s) on it, or write the registration(s) directly into the markup via
 * data attributes.
 *
 * Have a look at the plugin-validation-example.html in the repository for example usage.
 *
 * @author Sebastian Schlapkohl
 * @version 0.25 alpha
 **/



//--|VALIDATION-EXTENSION----------

$.extend($.jqueryAnnexData, {

	// plugin is stored as part of jqueryAnnexData-dictionary
	validation : {

		// general state of page wide validation and basic validation data structure for elements
		config : {
			isValid : true,
			messages : [],

			registeredTargets : {
				all : []
			},

			globalCallback : $.noop,

			additionalWidgetEvents : '',
			validateOnWidgetEvents : true,

			defaultValidationData : {
				isValid : true,

				isOptional : false,
				hasNonOptionalValue : false,
				optionalValues : [''],

				asyncError : false,
				asyncCount : 0,
				asyncLeft : 0,

				messages : [],

				values : []
			}
		},

		// helper functions for internal plugin use
		functions : {
			validate : function(targets, isTriggeredByWidget){

				if( !$.isSet(isTriggeredByWidget) ){
					isTriggeredByWidget = false;
				}

				if( !isTriggeredByWidget || (isTriggeredByWidget && $.jqueryAnnexData.validation.config.validateOnWidgetEvents) ){
					var validationRest = targets.length;

					$.jqueryAnnexData.validation.config.isValid = true;
					$.jqueryAnnexData.validation.config.messages = [];

					$.each(targets, function(index, $target){
						if( $.isSet($target) ){
							var validationData = $target.data('validationdata');

							// catch trailing elements, not cleaned properly
							if( $.isSet(validationData) ){
								$target.first().one('finished.validation', function(e, isValid){
									e.stopPropagation();

									$.jqueryAnnexData.validation.config.isValid = $.jqueryAnnexData.validation.config.isValid && isValid;

									if( !isValid ){
										$.merge($.jqueryAnnexData.validation.config.messages, $(e.target).data('validationdata').status.messages);
									}

									validationRest--;
									if( validationRest <= 0 ){
										$.jqueryAnnexData.validation.config.globalCallback($.jqueryAnnexData.validation.config.isValid, $.jqueryAnnexData.validation.config.messages);
										$(document).trigger('finished.validation', $.jqueryAnnexData.validation.config.isValid);
									}
								});

								var isOptional = validationData.status.isOptional;
								var asyncCount = validationData.status.asyncCount;
								validationData.status = $.extend(true, {}, $.jqueryAnnexData.validation.config.defaultValidationData);
								validationData.status.isOptional = isOptional;
								validationData.status.asyncCount = asyncCount;
								validationData.status.asyncLeft = asyncCount;

								validationData.status.values = validationData.container.formDataToObject()[$target.attr('name').replace(/\[\]/, '')];

								if( $.isSet(validationData.status.values) ){
									if( !$.isArray(validationData.status.values) ){
										validationData.status.values = [validationData.status.values];
									}
								} else {
									validationData.status.values = [];
								}

								if( validationData.status.isOptional ){
									$.each(validationData.status.values, function(index, value){
										if( $.inArray(value, validationData.status.optionalValues) == -1 ){
											validationData.status.hasNonOptionalValue = true;
											return false;
										}
									});
								}

								$target.data('validationdata', validationData);

								$.jqueryAnnexData.validation.functions.unmarkValidationError($target);

								if( !validationData.status.isOptional || validationData.status.hasNonOptionalValue ){
									$.each(validationData.rules, function(key, value){
										validationData.status.isValid = value() && validationData.status.isValid;
									});

									if( !validationData.status.isValid ){
										$.jqueryAnnexData.validation.functions.markValidationError($target);

										if( validationData.status.asyncLeft <= 0 ){
											if( $.isSet(validationData.callback) && $.isFunction(validationData.callback) ){
												validationData.callback(false, validationData.status.messages, $target);
											}

											$target.trigger('error.validation', validationData.status.messages);
										}
									}
								} else {
									validationData.status.asyncLeft = 0;
								}

								if( validationData.status.asyncLeft <= 0 ){
									if( validationData.status.isValid ){
										if( $.isSet(validationData.callback) && $.isFunction(validationData.callback) ){
											validationData.callback(true, [], $target);
										}
										$target.trigger('success.validation');
									}
									$target.trigger('finished.validation', validationData.status.isValid);
								}
							} else {
								// manually kill validation for elements with lost validation data
								$target.unsetValidation(true);
							}
						}
					});
				}
			},



			markValidationError : function($target){
				var $errorContainer = $target.data('validationdata').errorContainer;

				if( $.isSet($errorContainer) ){
					$errorContainer.addClass('validationerror');
				}

				$target.addClass('validationerror');
			},



			unmarkValidationError : function($target){
				var $errorContainer = $target.data('validationdata').errorContainer;

				if( $.isSet($errorContainer) ){
					$errorContainer.removeClass('validationerror');
				}

				$target.removeClass('validationerror');
			},



			asyncCallbackFactory : function($target, msg){
				var context = $target.data('validationdata').status;
				var callback = $target.data('validationdata').callback;

				var asyncCallBack = function(asyncRes){
					var res =
						$.isA(asyncRes, 'boolean')
						? asyncRes
						: ($.trim(''+asyncRes) === '')
					;

					context.asyncError = context.asyncError && !res;
					context.isValid = context.isValid && res;
					context.asyncLeft--;

					if( !context.isValid ){
						$.jqueryAnnexData.validation.functions.markValidationError($target);

						if( !res ){
							if( $.isA(asyncRes, 'string') && ($.trim(''+asyncRes) !== '') ){
								context.messages.push($.trim(''+asyncRes));
							} else {
								context.messages.push(msg);
							}
						}

						if( context.asyncLeft <= 0 ){
							if( $.isSet(callback) && $.isFunction(callback) ){
								callback(false, context.messages, $target);
							}

							$target.trigger('error.validation', context.messages);
						}
					} else if( context.asyncLeft <= 0 ){
						if( $.isSet(callback) && $.isFunction(callback) ){
							callback(true, [], $target);
						}

						$target.trigger('success.validation');
					}

					if( context.asyncLeft <= 0 ){
						$target.trigger('finished.validation', context.isValid);
					}
				};

				return asyncCallBack;
			},



			asyncErrorCallbackFactory : function($target){
				var context = $target.data('validationdata').status;
				var callback = $target.data('validationdata').callback;

				var asyncErrorCallBack = function(){
					context.asyncLeft--;
					context.asyncError = true;

					context.isValid = context.isValid && !context.asyncError;

					$.jqueryAnnexData.validation.functions.markValidationError($target);
					context.messages.push('communication error, could not retrieve data from server');
					$.log('communication error, could not retrieve data from server');

					if( context.asyncLeft <= 0 ){
						if( $.isSet(callback) && $.isFunction(callback) ){
							callback(false, context.messages, $target);
						}

						$target.trigger('error.validation', context.messages);
						$target.trigger('finished.validation', false);
					}
				};

				return asyncErrorCallBack;
			}
		},

		// rule methods to apply validation to values
		validators : {
			customcase : function(msg, customRes){
				var that = this;
				var res = true;
				var context = $(this).data('validationdata').status;
				var dynamicMessage = null;

				if( $.isArray(customRes) && (customRes.length < 2) ){
					customRes = customRes[0];
				}

				if( !$.isFunction(customRes) ){
					if( $.isA(customRes, 'boolean') ){
						res = customRes;
					} else if( $.trim(''+customRes) !== '' ){
						res = false;
						dynamicMessage =  $.trim(''+customRes);
					}
				} else {
					var tmpRes = customRes(
						$.jqueryAnnexData.validation.functions.asyncCallbackFactory($(this), msg),
						$.jqueryAnnexData.validation.functions.asyncErrorCallbackFactory($(this))
					);

					if( $.isSet(tmpRes) ){
						if( $.isA(tmpRes, 'boolean') ){
							res = tmpRes;
						} else if( $.trim(''+tmpRes) !== '' ){
							res = false;
							dynamicMessage =  $.trim(''+tmpRes);
						}
					}
				}

				if( !$.isSet(dynamicMessage) ){
					if( !res && $.isSet(msg) && ($.trim(msg) !== '') ){
						context.messages.push(msg);
					}
				} else {
					context.messages.push(msg);
				}

				return res;
			},



			required : function(msg){
				var res = true;
				var context = $(this).data('validationdata').status;

				if( context.values.length == 1 ){
					res = context.values[0] !== '';
				} else {
					res = context.values.length > 0;
				}

				if( !res && $.isSet(msg) && (msg !== '') ){
					context.messages.push(msg);
				}

				return res;
			},



			notempty : function(msg){
				var additionalEmptyValues = Array.prototype.slice.call(arguments, 1);

				var res = true;
				var context = $(this).data('validationdata').status;

				var emptyValues = [''];
				$.merge(emptyValues, additionalEmptyValues);

				if( context.values.length == 1 ){
					res = ($.inArray($.trim(context.values[0]), emptyValues) == -1);
				} else {
					res = context.values.length !== 0;
				}

				if( !res && $.isSet(msg) && ($.trim(msg) !== '') ){
					context.messages.push(msg);
				}

				return res;
			},



			optional : function(){
				return true;
			},



			minlength : function(msg, minlength){
				minlength = parseInt(minlength, 10);

				var res = true;
				var context = $(this).data('validationdata').status;

				if( context.values.length == 1 ){
					res = context.values[0].length >= minlength;
				} else {
					res = (context.values.length >= minlength);
				}

				if( !res && $.isSet(msg) && ($.trim(msg) !== '') ){
					context.messages.push(msg);
				}

				return res;
			},



			maxlength : function(msg, maxlength){
				maxlength = parseInt(maxlength, 10);

				var res = true;
				var context = $(this).data('validationdata').status;

				if( context.values.length == 1 ){
					res = context.values[0].length <= maxlength;
				} else {
					res = (context.values.length <= maxlength);
				}

				if( !res && $.isSet(msg) && ($.trim(msg) !== '') ){
					context.messages.push(msg);
				}

				return res;
			},



			rangelength : function(msg, rangemin, rangemax){
				var res = true;
				var context = $(this).data('validationdata').status;

				if( $.isSet(rangemin, rangemax) ){
					res =
						$.proxy($.jqueryAnnexData.validation.validators.minlength, $(this), null, rangemin)()
						&& $.proxy($.jqueryAnnexData.validation.validators.maxlength, $(this), null, rangemax)()
					;
				}

				if( !res && $.isSet(msg) && ($.trim(msg) !== '') ){
					context.messages.push(msg);
				}

				return res;
			},



			min : function(msg, min){
				var res = true;
				var context = $(this).data('validationdata').status;

				$.each(context.values, function(index, value){
					res = res && ($.isNumeric(value) && (parseInt(value, 10) >= min));
					if( !res ) return false;
				});

				if( !res && $.isSet(msg) && ($.trim(msg) !== '') ){
					context.messages.push(msg);
				}

				return res;
			},



			max : function(msg, max){
				var res = true;
				var context = $(this).data('validationdata').status;

				$.each(context.values, function(index, value){
					res = res && ($.isNumeric(value) && (parseInt(value, 10) <= max));
					if( !res ) return false;
				});

				if( !res && $.isSet(msg) && ($.trim(msg) !== '') ){
					context.messages.push(msg);
				}

				return res;
			},



			range : function(msg, min, max){
				var res = true;
				var context = $(this).data('validationdata').status;

				if( $.isSet(min, max) ){
					res =
						$.proxy($.jqueryAnnexData.validation.validators.min, $(this), null, min)()
						&& $.proxy($.jqueryAnnexData.validation.validators.max, $(this), null, max)()
					;
				}

				if( !res && $.isSet(msg) && ($.trim(msg) !== '') ){
					context.messages.push(msg);
				}

				return res;
			},



			email : function(msg){
				var res = true;
				var context = $(this).data('validationdata').status;

				$.each(context.values, function(index, value){
					var ruleRes = /^[^@]{1,64}@[^@]{1,255}$/.test(value);

					if( ruleRes ){
						var email_array = value.split('@');
						var local_array = email_array[0].split('.');

						for ( var i = 0; i < local_array.length; i++) {
							if( !(/^(([A-Za-z0-9!#$%&'*+\/=?^_`{|}~-][A-Za-z0-9!#$%&'*+\/=?^_`{|}~\.-]{0,63})|(\\\"[^(\\|\\\")]{0,62}\\\"))$/.test(local_array[i])) ){
								ruleRes = false;
							}
						}

						var domain_array = '';
						if( !(/^\[?[0-9\.]+\]?$/.test(email_array[1])) ){
							domain_array = email_array[1].split('.');

							if( domain_array.length < 2 ){
								ruleRes = false;
							}

							for( i = 0; i < domain_array.length; i++ ){
								if( !(/^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]{2,5}))$/.test(domain_array[i])) ){
									ruleRes = false;
								}
							}
						}
					}

					res = res && ruleRes;

					if( !res ) return false;
				});

				if( !res && $.isSet(msg) && ($.trim(msg) !== '') ){
					context.messages.push(msg);
				}

				return res;
			},



			url : function(msg){
				var res = true;
				var context = $(this).data('validationdata').status;

				$.each(context.values, function(index, value){
					res = res && /^(https?|ftp)\:\/\/([a-z0-9+!*(),;?&=$_.-]+(\:[a-z0-9+!*(),;?&=$_.-]+)?@)?([a-z0-9+$_-]+\.)*[a-z0-9+$_-]{2,3}(\:[0-9]{2,5})?(\/([a-z0-9+$_-]\.?)+)*\/?(\?[a-z+&$_.-][a-z0-9;:@\/&%=+$_.-]*)?(#[a-z_.-][a-z0-9+$_.-]*)?$/.test(value);
					if( !res ) return false;
				});

				if( !res && $.isSet(msg) && ($.trim(msg) !== '') ){
					context.messages.push(msg);
				}

				return res;
			},



			date : function(msg, __internal__){
				var res = true;
				var context = $(this).data('validationdata').status;

				$.each(context.values, function(index, value){
					if( $.isSet(__internal__) && (__internal__ == '__internal__') ){
						var datetimeParts = value.split(' ');
						if( datetimeParts.length >= 2 ){
							value = $.trim(datetimeParts[0]);
						} else {
							value = '';
						}
					}

					var formatValid = /^(0?[1-9]|1[0-2])\/(0?[1-9]|[1-2][0-9]|3[0-1])\/([1-2][0-9]{3})$/.test(value);
					var splitValue = formatValid ? value.split('/') : null;

					if( formatValid ){
						for( var i = 0; i < 2; i++ ){
							if( splitValue[i].length  < 2 ){
								splitValue[i] = '0'+splitValue[i];
							}
						}
					}

					var date = formatValid ? new Date(splitValue[2]+'-'+splitValue[0]+'-'+splitValue[1]) : null;
					var ruleRes =
						((date !== null) && (splitValue !== null))
						? (
							!/Invalid|NaN/.test(date)
							&& (parseInt(splitValue[0], 10) == (date.getMonth() + 1))
							&& (parseInt(splitValue[1], 10) == (date.getDate()))
							&& (parseInt(splitValue[2], 10) == (date.getFullYear()))
						)
						: false
					;

					res = res && ruleRes;

					if( !res ) return false;
				});

				if( !res && $.isSet(msg) && ($.trim(msg) !== '') ){
					context.messages.push(msg);
				}

				return res;
			},



			time : function(msg, __internal__){
				var res = true;
				var context = $(this).data('validationdata').status;

				$.each(context.values, function(index, value){
					if( $.isSet(__internal__) && (__internal__ == '__internal__') ){
						var datetimeParts = value.split(' ');
						if( datetimeParts.length >= 2 ){
							value = $.trim(datetimeParts[1]);
							if( datetimeParts.length >= 3 ){
								value += ' '+$.trim(datetimeParts[2]);
							}
						} else {
							value = '';
						}
					}

					var ruleRes = /^((0?[0-9]|1[0-1])\:[0-5][0-9](\:[0-5][0-9])? ?(am|AM|pm|PM)|12\:[0-5][0-9](\:[0-5][0-9])? ?(pm|PM))$/.test(value);
					res = res && ruleRes;
					if( !res ) return false;
				});

				if( !res && $.isSet(msg) && ($.trim(msg) !== '') ){
					context.messages.push(msg);
				}

				return res;
			},



			datetime : function(msg){
				var res = true;
				var context = $(this).data('validationdata').status;

				res =
					$.proxy($.jqueryAnnexData.validation.validators.date, $(this), null, '__internal__')()
					&& $.proxy($.jqueryAnnexData.validation.validators.time, $(this), null, '__internal__')()
				;

				if( !res && $.isSet(msg) && ($.trim(msg) !== '') ){
					context.messages.push(msg);
				}

				return res;
			},



			dateISO : function(msg, __internal__){
				var res = true;
				var context = $(this).data('validationdata').status;

				$.each(context.values, function(index, value){
					if( $.isSet(__internal__) && (__internal__ == '__internal__') ){
						var datetimeParts = value.replace(/T/, ' ').split(' ');
						if( datetimeParts.length >= 2 ){
							value = $.trim(datetimeParts[0]);
						} else {
							value = '';
						}
					}

					var formatValid = /^([1-2][0-9]{3})\-(0?[1-9]|1[0-2])\-(0?[1-9]|[1-2][0-9]|3[0-1])$/.test(value);
					var splitValue = formatValid ? value.split('-') : null;

					if( formatValid ){
						for( var i = 1; i < 3; i++ ){
							if( splitValue[i].length  < 2 ){
								splitValue[i] = '0'+splitValue[i];
							}
						}
					}

					var date = formatValid ? new Date(splitValue.join('-')) : null;
					var ruleRes =
						((date !== null) && (splitValue !== null))
						? (
							!/Invalid|NaN/.test(date)
							&& (parseInt(splitValue[0], 10) == (date.getFullYear()))
							&& (parseInt(splitValue[1], 10) == (date.getMonth() + 1))
							&& (parseInt(splitValue[2], 10) == (date.getDate()))
						)
						: false
					;

					res = res && ruleRes;
					if( !res ) return false;
				});

				if( !res && $.isSet(msg) && ($.trim(msg) !== '') ){
					context.messages.push(msg);
				}

				return res;
			},



			timeISO : function(msg, __internal__){
				var res = true;
				var context = $(this).data('validationdata').status;

				$.each(context.values, function(index, value){
					if( $.isSet(__internal__) && (__internal__ == '__internal__') ){
						var datetimeParts = value.replace(/T/, ' ').split(' ');
						if( datetimeParts.length >= 2 ){
							value = $.trim(datetimeParts[1]);
						} else {
							value = '';
						}
					}

					var ruleRes = /^([0-1][0-9]|2[0-3])\:[0-5][0-9]\:[0-5][0-9]$/.test(value);
					res = res && ruleRes;
					if( !res ) return false;
				});

				if( !res && $.isSet(msg) && ($.trim(msg) !== '') ){
					context.messages.push(msg);
				}

				return res;
			},



			datetimeISO : function(msg){
				var res = true;
				var context = $(this).data('validationdata').status;

				res =
					$.proxy($.jqueryAnnexData.validation.validators.dateISO, $(this), null, '__internal__')()
					&& $.proxy($.jqueryAnnexData.validation.validators.timeISO, $(this), null, '__internal__')()
				;

				if( !res && $.isSet(msg) && ($.trim(msg) !== '') ){
					context.messages.push(msg);
				}

				return res;
			},



			dateDE : function(msg, __internal__){
				var res = true;
				var context = $(this).data('validationdata').status;

				$.each(context.values, function(index, value){
					if( $.isSet(__internal__) && (__internal__ == '__internal__') ){
						var datetimeParts = value.split(' ');
						if( datetimeParts.length >= 2 ){
							value = $.trim(datetimeParts[0]);
						} else {
							value = '';
						}
					}

					var formatValid = /^(0?[1-9]|[1-2][0-9]|3[0-1])\.(0?[1-9]|1[0-2])\.([1-2][0-9]{3})$/.test(value);
					var splitValue = formatValid ? value.split('.') : null;

					if( formatValid ){
						for( var i = 0; i < 2; i++ ){
							if( splitValue[i].length  < 2 ){
								splitValue[i] = '0'+splitValue[i];
							}
						}
					}

					var date = formatValid ? new Date(splitValue[2]+'-'+splitValue[1]+'-'+splitValue[0]) : null;
					var ruleRes =
						((date !== null) && (splitValue !== null))
						? (
							!/Invalid|NaN/.test(date)
							&& (parseInt(splitValue[0], 10) == (date.getDate()))
							&& (parseInt(splitValue[1], 10) == (date.getMonth() + 1))
							&& (parseInt(splitValue[2], 10) == (date.getFullYear()))
						)
						: false
					;

					res = res && ruleRes;
					if( !res ) return false;
				});

				if( !res && $.isSet(msg) && ($.trim(msg) !== '') ){
					context.messages.push(msg);
				}

				return res;
			},



			timeDE : function(msg, __internal__){
				var res = true;
				var context = $(this).data('validationdata').status;

				$.each(context.values, function(index, value){
					if( $.isSet(__internal__) && (__internal__ == '__internal__') ){
						var datetimeParts = value.split(' ');
						if( datetimeParts.length >= 2 ){
							value = $.trim(datetimeParts[1]);
						} else {
							value = '';
						}
					}

					var ruleRes = /^([0-1][0-9]|2[0-3])\:[0-5][0-9](\:[0-5][0-9])?h?$/.test(value);
					res = res && ruleRes;
					if( !res ) return false;
				});

				if( !res && $.isSet(msg) && ($.trim(msg) !== '') ){
					context.messages.push(msg);
				}

				return res;
			},



			datetimeDE : function(msg){
				var res = true;
				var context = $(this).data('validationdata').status;

				res =
					$.proxy($.jqueryAnnexData.validation.validators.dateDE, $(this), null, '__internal__')()
					&& $.proxy($.jqueryAnnexData.validation.validators.timeDE, $(this), null, '__internal__')()
				;

				if( !res && $.isSet(msg) && ($.trim(msg) !== '') ){
					context.messages.push(msg);
				}

				return res;
			},



			number : function(msg){
				var res = true;
				var context = $(this).data('validationdata').status;

				$.each(context.values, function(index, value){
					res = res && ((value == ''+parseInt(value, 10)) || (value == ''+parseFloat(value)));
					if( !res ) return false;
				});

				if( !res && $.isSet(msg) && ($.trim(msg) !== '') ){
					context.messages.push(msg);
				}

				return res;
			},



			numberDE : function(msg){
				var res = true;
				var context = $(this).data('validationdata').status;

				$.each(context.values, function(index, value){
					var ruleRes = /^[0-9]+(\,[0-9]+)?$/.test(value);

					if( ruleRes ){
						value = value.replace(/\,/g, '.');
						ruleRes = ((value == ''+parseInt(value, 10)) || (value == ''+parseFloat(value)));
					}

					res = res && ruleRes;
					if( !res ) return false;
				});

				if( !res && $.isSet(msg) && ($.trim(msg) !== '') ){
					context.messages.push(msg);
				}

				return res;
			},



			digits : function(msg){
				var res = true;
				var context = $(this).data('validationdata').status;

				$.each(context.values, function(index, value){
					res = res && /^[0-9]+$/.test(value);
					if( !res ) return false;
				});

				if( !res && $.isSet(msg) && ($.trim(msg) !== '') ){
					context.messages.push(msg);
				}

				return res;
			},



			creditcard : function(msg){
				var res = true;
				var context = $(this).data('validationdata').status;

				$.each(context.values, function(index, value){
					res = res && /^[0-9]{3,4}\-[0-9]{4}\-[0-9]{4}\-[0-9]{4}$/.test(value);
					if( !res ) return false;
				});

				if( !res && $.isSet(msg) && ($.trim(msg) !== '') ){
					context.messages.push(msg);
				}

				return res;
			},



			characterclass : function(msg, characterclass){
				var res = true;
				var context = $(this).data('validationdata').status;

				var characterClassRegEx = new RegExp('^['+characterclass+']*$');

				$.each(context.values, function(index, value){
					res = res && characterClassRegEx.test(value);
					if( !res ) return false;
				});

				if( !res && $.isSet(msg) && ($.trim(msg) !== '') ){
					context.messages.push(msg);
				}

				return res;
			}
		}
	}

});



//--|JQUERY-$-GENERAL-FUNCTIONS----------

$.extend({

	triggerValidation : function(targetGroup){
		targetGroup = $.isSet(targetGroup) ? ''+targetGroup : 'all';
		$.jqueryAnnexData.validation.functions.validate($.jqueryAnnexData.validation.config.registeredTargets[targetGroup]);
	},



	executeOnValidation : function(callback){
		$.assert($.isFunction(callback), 'executeOnValidation | callback is no function');

		$.jqueryAnnexData.validation.config.globalCallback = callback;
	},



	validationIsTriggeredByWidgetEvents : function(doValidate){
		doValidate = doValidate ? true : false;
		$.jqueryAnnexData.validation.config.validateOnWidgetEvents = doValidate;
	},



	setAdditionalValidationWidgetEvents : function(events){
		$.assert($.isArray(events), 'events need to be an array');

		var eventString = '';
		$.each(events, function(index, value){
			eventString += value+'.validation ';
		});
		eventString = ' '+$.trim(eventString);

		$.jqueryAnnexData.validation.config.additionalWidgetEvents = eventString;
	},



	setValidationFromTags : function(){
		$(':input[data-validation]').each(function(){
			if( $.isPlainObject($(this).data('validation')) || $.isArray($(this).data('validation')) ){
				var callback = ($.isSet($(this).data('validation-callback')) && $.isFunction($(this).data('validation-callback'))) ? $(this).data('validation-callback') : null;
				var errorContainer = ($.isSet($(this).data('validation-errorcontainer')) && $.exists($(this).data('validation-errorcontainer'))) ? $(this).data('validation-errorcontainer') : null;
				var container = ($.isSet($(this).data('validation-container')) && $.exists($(this).data('validation-container'))) ? $(this).data('validation-container') : null;
				var targetGroup = ($.isSet($(this).data('validation-targetgroup'))) ? $(this).data('validation-targetgroup') : null;
				var suppressSubmit = ($.isSet($(this).data('validation-suppresssubmit'))) ? ($(this).data('validation-suppresssubmit') == 'true') : false;
				$(this).setValidation($(this).data('validation'), callback, errorContainer, container, targetGroup, suppressSubmit);
			}
		});
	}

});



//--|JQUERY-OBJECT-GENERAL-FUNCTIONS----------

$.fn.extend({

	setValidation : function(validators, callback, $errorContainer, $container, targetGroup, suppressSubmit){
		if( !$.isSet(validators) ){
			validators = [];
		}
		if( !$.isArray(validators) ){
			validators = [validators];
		}

		targetGroup = $.isSet(targetGroup) ? ''+targetGroup : 'all';

		suppressSubmit = $.isSet(suppressSubmit) ? (suppressSubmit === true) : false;

		if( !$.isSet($container) ){
			$container = $(this).closest('form');

			if( !$.exists($container) ){
				$container = $(this);
			} else {
				$container.off('submit.validation');
				$container.on('submit.validation', function(e, validated){
					if( !$.isSet(validated) ){
						e.preventDefault();

						$(document).one('finished.validation', function(e, isValid){
							$container.trigger('submit.validation', isValid);
						});
						$.jqueryAnnexData.validation.functions.validate($.jqueryAnnexData.validation.config.registeredTargets[targetGroup]);
					} else if( !validated || suppressSubmit ){
						e.preventDefault();
					}
				});
			}
		} else {
			$container = $container.find(':input');
		}

		// apply to each set member individually to avoid setting data for whole set
		$(this).each(function(){
			$.assert($(this).is(':input'), 'setValidation | element is no value-bearing form element');
			$.assert($.isSet($(this).attr('name')), 'setValidation | element has no attribute "name"');

			var that = this;

			$(this).removeData('validationdata');
			var validationData = {
				status : $.extend({}, $.jqueryAnnexData.validation.config.defaultValidationData),
				rules : {},
				callback : callback,
				container : $container,
				errorContainer : $errorContainer
			};

			var asyncRulesCount = 0;
			$.each(validators, function(index, rule){
				var ruleLength = $.objectLength(rule);
				if( $.isPlainObject(rule) && (ruleLength >= 1) && (ruleLength <= 2) ){
					if( ruleLength == 2 ){
						$.assert($.exists('args', rule));
					}

					var ruleName = null;
					var ruleMessage = null;
					var ruleArgs = $.exists('args', rule) ? ($.isArray(rule.args) ? rule.args : [rule.args]) : [];
					$.each(rule, function(rulePartKey, rulePartValue){
						if( rulePartKey != 'args' ){
							ruleName = rulePartKey;
							ruleMessage = rulePartValue;
							return false;
						}
					});

					if( $.isSet(ruleName) ){
						ruleName = ruleName.split('_async');
						var hasAsyncMarker = (ruleName.length > 1);
						ruleName = ruleName[0];

						if( ruleName == 'optional' ){
							validationData.status.isOptional = true;
							$.merge(validationData.status.optionalValues, ruleArgs);
						}

						if( $.isFunction($.jqueryAnnexData.validation.validators[ruleName]) ){
							if( hasAsyncMarker ){
								asyncRulesCount++;
							}

							validationData.rules[ruleName+'_'+$.randomUUID(true)] = $.proxy.apply($, $.merge([], $.merge([$.jqueryAnnexData.validation.validators[ruleName], $(that), ''+$.trim(ruleMessage)], ruleArgs)));
						}
					}
				}
			});
			validationData.status.asyncCount = asyncRulesCount;

			$.jqueryAnnexData.validation.config.registeredTargets['all'].push($(this));
			if( targetGroup != 'all' ){
				if( !$.isSet($.jqueryAnnexData.validation.config.registeredTargets[targetGroup]) ){
					$.jqueryAnnexData.validation.config.registeredTargets[targetGroup] = [];
				}
				$.jqueryAnnexData.validation.config.registeredTargets[targetGroup].push($(this));
			}

			var blurChangeTimeout = null;
			$(this)
				.data('validationdata', validationData)
				.on('change.validation blur.validation'+$.jqueryAnnexData.validation.config.additionalWidgetEvents, function(){
					$.countermand(blurChangeTimeout);
					blurChangeTimeout = $.schedule(10, function(){
						$.jqueryAnnexData.validation.functions.validate($.jqueryAnnexData.validation.config.registeredTargets[targetGroup], true);
					});
				})
			;
		});

		return this;
	},


	unsetValidation : function(ignoreMissingValidationData, targetGroup){
		ignoreMissingValidationData = $.isSet(ignoreMissingValidationData) ? ignoreMissingValidationData : false;
		targetGroup = ($.isSet(targetGroup) && $.jqueryAnnexData.validation.config.registeredTargets[targetGroup]) ? ''+targetGroup : null;

		// handle all set members individually to clearly identify them in registeredTarget-array
		$(this).each(function(){
			$.assert($(this).is(':input'), 'unsetValidation | element is no value-bearing form element');
			$.assert($.isSet($(this).attr('name')), 'unsetValidation | element has no attribute "name"');

			var _this_ = this;

			if( $.isSet($(this).data('validationdata')) || ignoreMissingValidationData ){
				$(this).removeData('validationdata');

				var groupsToSearch = {};

				if( $.isSet(targetGroup) ){
					groupsToSearch['all'] = $.jqueryAnnexData.validation.config.registeredTargets['all'];
					groupsToSearch[targetGroup] = $.jqueryAnnexData.validation.config.registeredTargets[targetGroup];
				} else {
					groupsToSearch = $.jqueryAnnexData.validation.config.registeredTargets;
				}

				$.each(groupsToSearch, function(key, targetGroup){
					if( $.isSet(targetGroup) ){
						var elementIndex = -1;
						for( var i = 0; i < targetGroup.length; i++ ){
							if( $(_this_).attr('name') == targetGroup[i].attr('name') ){
								elementIndex = i;
								break;
							}
						}

						if( elementIndex >= 0 ){
							$.removeFromArray($.jqueryAnnexData.validation.config.registeredTargets[key], elementIndex);
						}

						if( $.jqueryAnnexData.validation.config.registeredTargets[key].length === 0 ){
							$(this).closest('form').off('submit.validation');

							if( key != 'all' ){
								delete $.jqueryAnnexData.validation.config.registeredTargets[key];
							}
						}
					}
				});

				$(this).off('change.validation blur.validation'+$.jqueryAnnexData.validation.config.additionalWidgetEvents);
			}
		});

		return this;
	}

});